using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Pine.Core;
using Pine.SourceParser.Tree;
using PPT=Pine.SourceParser.Tree;

namespace Pine.CodeGen
{
	public static class BinaryOperationCodeGenerator
	{
		private static IDictionary<PPT.OperationType, MethodInfo> binaryMethods = new Dictionary<PPT.OperationType, MethodInfo>();
		
		static BinaryOperationCodeGenerator()
		{
			binaryMethods[PPT.OperationType.Sum] = typeof(Value).GetMethod("Add");
			binaryMethods[PPT.OperationType.Minus] = typeof(Value).GetMethod("Subtract");
			binaryMethods[PPT.OperationType.Multiplication] = typeof(Value).GetMethod("Multiply");
			binaryMethods[PPT.OperationType.Less] = typeof(Value).GetMethod("Less");
			binaryMethods[PPT.OperationType.LessOrEqual] = typeof(Value).GetMethod("LessOrEqual");
			binaryMethods[PPT.OperationType.Greater] = typeof(Value).GetMethod("Greater");
			binaryMethods[PPT.OperationType.GreaterOrEqual] = typeof(Value).GetMethod("GreaterOrEqual");
			binaryMethods[PPT.OperationType.Equal] = typeof(Value).GetMethod("IsEqualTo");
			binaryMethods[PPT.OperationType.NotEqual] = typeof(Value).GetMethod("NotEqualTo");
		}
		
		public static void GenerateBytecode(Context ctx, Expression expr, LocalBuilder lb) {
			BinaryOperation bop = (BinaryOperation) expr;
			
			//Short-circuited operators require special treatment
			if (bop.type == PPT.OperationType.LogicalAnd)
				BuildLogicalAnd(ctx, bop.left, bop.right, lb);
			else if (bop.type == PPT.OperationType.LogicalOr)
				BuildLogicalOr(ctx, bop.left, bop.right, lb);
			else
				BuildBinaryAny(ctx, bop, lb, binaryMethods[bop.type]);
		}
		
		private static void BuildLogicalAnd(Context ctx, Expression left, Expression right, LocalBuilder lb) {
			left.GenerateBytecode(ctx, lb);
			
			Label EndLabel = ctx.il.DefineLabel();
			
			ctx.il.Emit(OpCodes.Ldloc, lb);
			ctx.il.Emit(OpCodes.Callvirt, typeof(Value).GetMethod("ToBoolean"));
			ctx.il.Emit(OpCodes.Brfalse, EndLabel);
			
			right.GenerateBytecode(ctx, lb);
			
			ctx.il.MarkLabel(EndLabel);
		}
		
		private static void BuildLogicalOr(Context ctx, Expression left, Expression right, LocalBuilder lb) {
			left.GenerateBytecode(ctx, lb);
			
			Label EndLabel = ctx.il.DefineLabel();
			
			ctx.il.Emit(OpCodes.Ldloc, lb);
			ctx.il.Emit(OpCodes.Callvirt, typeof(Value).GetMethod("ToBoolean"));
			ctx.il.Emit(OpCodes.Brtrue, EndLabel);
			
			right.GenerateBytecode(ctx, lb);
			
			ctx.il.MarkLabel(EndLabel);
		}
		
		private static void BuildBinaryAny(Context ctx, BinaryOperation bop, LocalBuilder lb, MethodInfo method) {
			LocalBuilder left = ctx.il.DeclareLocal(typeof(Value));
			bop.left.GenerateBytecode(ctx, left);
			LocalBuilder right = ctx.il.DeclareLocal(typeof(Value));
			bop.right.GenerateBytecode(ctx, right);
			
			ctx.il.Emit(OpCodes.Ldloc, left);
			ctx.il.Emit(OpCodes.Ldloc, right);
			ctx.il.Emit(OpCodes.Callvirt, method);
			ctx.il.Emit(OpCodes.Stloc, lb);
		}
	}
}

